﻿using System;
using System.Xml;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace HotelReservation
{
    public struct Hotel
    {
        public string HotelName;
        public Byte Rating;
        public Hashtable PricePolicy;
    }

    public class HotelReservSrv
    {
        public List<Hotel> Hotels
        {
            get
            {
                return _Hotels;
            }
        }

        public List<string> PricePolicies
        {
            get
            {
                return _PricePolicies;
            }
        }

        public HotelReservSrv(XmlDocument doc = null)
        {
            xmlDoc = doc;
            _Hotels = new List<Hotel>();
            _PricePolicies = new List<string>();
        }

        public bool LoadConfig(string path)
        {
            XmlDocument doc = new XmlDocument();
            if (path != null)
            {
                try
                {
                    doc.Load(path);
                    xmlDoc = doc;
                    return ParseConfig();
                }
                catch (Exception e)
                {
                    Log("Unable to load or parse config file, reason: " + e.Message);
                }
            }
            return false;
        }

        public bool ParseConfig()
        {
            if (xmlDoc == null)
                throw new Exception("The config file is not correctly loaded");

            List<Hotel> HotelsFromConfig = new List<Hotel>();
            List<string> PricesFromConfig = new List<string>();

            XmlNode root = xmlDoc.DocumentElement;
            XmlNode PricePolicyNode = root.SelectSingleNode("PricePolicy");
            XmlNode HotelsNode = root.SelectSingleNode("Hotels");

            Console.WriteLine("\n==== Parsing Config File ====\n\n");

            if (PricePolicyNode == null)
            {
                Log("The 'PricePolicy' tag is absent in config file");
                return false;
            }

            if (HotelsNode == null)
            {
                Log("The 'Hotels' tag is absent in config file");
                return false;
            }

            foreach (XmlNode node in PricePolicyNode.ChildNodes)
            {
                string Policy = node.InnerText.ToLower();
                if (Policy.Length > 0)
                {
                    PricesFromConfig.Add(Policy);
                }
            }

            foreach (XmlNode HotelNode in HotelsNode.ChildNodes)
            {
                Hotel hotel = new Hotel();
                hotel.HotelName = HotelNode.Attributes["Name"].InnerText;
                if (hotel.HotelName.Trim().Length == 0)
                {
                    Log("The Hotel Name Can not be Empty");
                    return false;
                }

                try
                {
                    hotel.Rating = Convert.ToByte(HotelNode.Attributes["Rating"].InnerText);
                }
                catch (Exception)
                {
                    Log("Hotel Rating must be a number");
                    return false;
                }

                hotel.PricePolicy = new Hashtable();

                HotelsFromConfig.Add(hotel);

                Console.WriteLine("Set Prices For " + hotel.HotelName + " Rating: " + hotel.Rating);

                foreach (XmlNode PriceCategory in HotelNode.ChildNodes)
                {
                    string LowerPriceName = PriceCategory.Name.ToLower();
                    if (!PricesFromConfig.Contains(LowerPriceName))
                    {
                        Log(PriceCategory.Name + " is not predefined in <PricePolicy> Entry.");
                        return false;
                    }

                    Hashtable PriceConfig = new Hashtable();
                    Array FromSundayWeeklyPrices = Array.CreateInstance(typeof(Int32), 7);
                    PriceConfig.Add("Weekly", FromSundayWeeklyPrices);

                    hotel.PricePolicy.Add(LowerPriceName, PriceConfig);

                    Int32 WeekdayPrice, WeekendPrice;
                    try
                    {
                        WeekdayPrice = Convert.ToInt32(PriceCategory.Attributes["Weekday"].InnerText);
                        WeekendPrice = Convert.ToInt32(PriceCategory.Attributes["Weekend"].InnerText);
                    }
                    catch (Exception)
                    {
                        Log("The Price for Weekday or Weekend is not correct");
                        return false;
                    }

                    for (int day = 1; day <= 5; day++)
                    {
                        FromSundayWeeklyPrices.SetValue(WeekdayPrice, day);
                    }

                    FromSundayWeeklyPrices.SetValue(WeekendPrice, 0);
                    FromSundayWeeklyPrices.SetValue(WeekendPrice, 6);

                    foreach (XmlNode Special in PriceCategory.ChildNodes)
                    {
                        string SpecialType = Special.Attributes["Type"].InnerText.Trim();
                        string SpeicalDay = Special.Attributes["Day"].InnerText.Trim();
                        if( SpecialType.Length == 0 || SpeicalDay.Length == 0 )
                            continue;

                        Int32 SpecialPrice;
                        try
                        {
                            SpecialPrice = Convert.ToInt32(Special.InnerText);
                        }
                        catch (Exception)
                        {
                            Log("Price In Speicial Tag is incorrect on " + SpeicalDay);
                            return false;
                        }

                        if ( SpecialType == "DayOfWeek")
                        {
                            int Index = Utils.Weekday(SpeicalDay);
                            FromSundayWeeklyPrices.SetValue(SpecialPrice, Index);
                        }
                        else if (SpecialType == "Date")
                        {
                            SpeicalDay = SpeicalDay.Trim();
                            string[] YearMonthDay = SpeicalDay.Split('-');
                            if (YearMonthDay.Length == 3)
                            {
                                PriceConfig.Add(SpeicalDay, SpecialPrice);
                            }
                            else
                            {
                                Log(SpeicalDay + " has incorrect format, please use yyyy-mm-dd");
                                return false;
                            }
                        }
                        
                    }
                }
            }

            _Hotels.Clear();
            _Hotels.AddRange(HotelsFromConfig);

            _PricePolicies.Clear();
            _PricePolicies.AddRange(PricesFromConfig);
            return true;

        }

        public string FindTheCheapestHotel(List<DateTime> Days, string PriceType)
        {
            string PriceCategory = PriceType.ToLower();
            if (!PricePolicies.Contains(PriceCategory))
            {
                Console.WriteLine(PriceType + "is not a valid Price Type");
                return null;
            }

            Hotel TheCheapestHotel = new Hotel();
            Int32 PriceSum = -1;
            foreach (Hotel hotel in Hotels)
            {
                Hashtable PriceLookup = (Hashtable)hotel.PricePolicy[PriceCategory];
                Array WeeklyPrices = (Array)PriceLookup["Weekly"];

                Int32 ThisHotelSum = 0;
                foreach (DateTime dt in Days)
                {
                    string DateKey = string.Format("{0:D}-{1:D}-{2:D}", dt.Year, dt.Month, dt.Day);
                    if (PriceLookup[DateKey] != null)
                    {
                        ThisHotelSum += (Int32)PriceLookup[DateKey];
                    }
                    else
                    {
                        ThisHotelSum += (Int32)WeeklyPrices.GetValue((int)dt.DayOfWeek);
                    }
                }

                if (PriceSum < 0)
                {
                    TheCheapestHotel = hotel;
                    PriceSum = ThisHotelSum;
                }
                else if (ThisHotelSum < PriceSum)
                {
                    TheCheapestHotel = hotel;
                    PriceSum = ThisHotelSum;
                }
                else if (ThisHotelSum == PriceSum)
                {
                    if (TheCheapestHotel.Rating < hotel.Rating)
                    {
                        TheCheapestHotel = hotel;
                    }
                }
            }

            return TheCheapestHotel.HotelName;

        }

        //private object ReloadMutex;
        private XmlDocument xmlDoc;

        private List<Hotel> _Hotels;
        private List<string> _PricePolicies;

        private void Log(string msg)
        {
            Console.WriteLine("Config Error: " + msg);
        }
    }

    public class Utils
    {
        static public int Month(string m)
        {
            if(MonthMap == null)
            {
                InitMonths();
            }
            return (int)MonthMap[m];
        }

        static public int Weekday(string d)
        {
            if (WeekdayMap == null)
            {
                InitWeekdays();
            }
            return (int)WeekdayMap[d];
        }


        static private Hashtable MonthMap;
        static private Hashtable WeekdayMap;

        static private void InitMonths()
        {
            MonthMap = new Hashtable();
            MonthMap.Add("jan", 1);
            MonthMap.Add("feb", 2);
            MonthMap.Add("mar", 3);
            MonthMap.Add("apr", 4);
            MonthMap.Add("may", 5);
            MonthMap.Add("jun", 6);
            MonthMap.Add("jul", 7);
            MonthMap.Add("aug", 8);
            MonthMap.Add("sept", 9);
            MonthMap.Add("oct", 10);
            MonthMap.Add("nov", 11);
            MonthMap.Add("dec", 12);
        }

        static private void InitWeekdays()
        {
            WeekdayMap = new Hashtable();
            WeekdayMap.Add("mon", 1);
            WeekdayMap.Add("tues", 2);
            WeekdayMap.Add("wed", 3);
            WeekdayMap.Add("thur", 4);
            WeekdayMap.Add("fri", 5);
            WeekdayMap.Add("sat", 6);
            WeekdayMap.Add("sun", 0);
        }
    }
}